package com.ant.Interceptor;

import com.alibaba.fastjson.JSON;
import com.ant.annotation.AccessLimit;
import com.ant.domain.AccessInfo;
import com.ant.domain.Result;
import com.ant.domain.StatusCode;
import com.ant.util.ApplicationContextUtil;
import com.ant.util.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author 党怡承
 * @version 1.0
 * @date 2021-08-04 9:09
 * @description 访问控制拦截器
 * @copyright 2021
 */
@Component
public class AccessInterceptor extends HandlerInterceptorAdapter {
  @Autowired
  private RedisUtil redisUtil;
  // ============================================解决@component注解下@autowired的类为null==============================================
  /** 第一种、声明一个此类的静态变量，用于保存bean */
  public static AccessInterceptor accessInterceptor;
  /** 将需要注入的类添加到静态变量中 */
  @PostConstruct
  public void init() {
    accessInterceptor = this;
    accessInterceptor.redisUtil = this.redisUtil;
  }
  // 模拟数据存储，实际业务中可以自定义实现方式
  public static Map<String, AccessInfo> accessInfoMap = new HashMap<>();

  @Override
  public boolean preHandle(
      HttpServletRequest request, HttpServletResponse response, Object handle) {
    // 第二种、编写工具类实现applicationContextAwared接口，重写setApplicationContext()方法
    RedisUtil redisUtil = (RedisUtil) ApplicationContextUtil.getBean("redisUtil");
    // 判断请求是否属于方法的请求
    if (handle instanceof HandlerMethod) {
      HandlerMethod handlerMethod = (HandlerMethod) handle;
      //       获取方法中的注解，看是否有该注解
      AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);
      if (accessLimit == null) {
        return true;
      }

      // 限制周期(单位、秒)
      int seconds = accessLimit.seconds();
      // 规定周期内限制次数
      int maxCount = accessLimit.maxCount();
      // 是否需要登录
      boolean login = accessLimit.needLogin();

      String key = request.getRequestURI();

      // 如果需要登录
      if (login) {
        /** 获取登录session进行判断 业务实现...... */
        // 这里假设用户是用户A，实际项目中可以改为userId
        key += "_userA";
      }
      // 模拟从redis中获取数据，暂时存储进入内存
      AccessInfo accessInfo = accessInfoMap.get(key);
      if (accessInfo == null) {
        // 第一次访问
        accessInfo = new AccessInfo();
        accessInfo.setFirstVisitTimeStamp(System.currentTimeMillis());
        accessInfo.setAccessCount(1);
        accessInfoMap.put(key, accessInfo);
      }
      if (accessInfo.getAccessCount() < maxCount) {
        // 访问次数加1
        accessInfo.setAccessCount(accessInfo.getAccessCount() + 1);
        accessInfoMap.put(key, accessInfo);
      } else {
        // 超出访问次数----判断时间是否超出设定时间
        if (System.currentTimeMillis() - accessInfo.getFirstVisitTimeStamp() <= seconds * 1000) {
          // 如果还在设定时间内，则为不合法请求，返回错误信息
          sendMessageToPage(response, "到达访问限制次数，请稍后重试");
          return false;
        } else {
          // 如果超出设定时间内，则为合法请求，将之前的请求清空，重新计数
          accessInfo.setFirstVisitTimeStamp(System.currentTimeMillis());
          accessInfo.setAccessCount(1);
          accessInfoMap.put(key, accessInfo);
        }
      }

      // 模拟从redis中获取数据
      // 已访问次数
      Object obj = redisUtil.get(key);
      if (Objects.isNull(obj)) {
        //        第一次访问
        redisUtil.incr(key, 1);
        redisUtil.expire(key, seconds);
      }
      // 获取单位时间内已访问次数
      int count = Integer.valueOf(redisUtil.get(key).toString());
      if (maxCount > count) {
        // 没超过访问限制
        redisUtil.incr(key, 1);
      } else {
        // 如果还在设定时间内，则为不合法请求，返回错误信息
        sendMessageToPage(response, "到达访问限制次数，请稍后重试");
        return false;
      }
    }
    return true;
  }

  /**
   * 向页面发送消息
   *
   * @param response
   * @param message
   */
  private void sendMessageToPage(HttpServletResponse response, String message) {
    try {
      response.setContentType("application/json; charset=UTF-8");
      OutputStream stream = response.getOutputStream();
      String str = JSON.toJSONString(new Result(true, StatusCode.ACCRESS_ERROR, message));
      stream.write(str.getBytes("UTF-8"));
      stream.flush();
      stream.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}
